/* Copyright (c) 2000-2006 hamcrest.org
*/
package org.hamcrest.beans;
import static org.hamcrest.beans.PropertyUtil.NO_ARGUMENTS;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeDiagnosingMatcher;
/**
* Matcher that asserts that a JavaBean property on an argument passed to the
* mock object meets the provided matcher. This is useful for when objects
* are created within code under test and passed to a mock object, and you wish
* to assert that the created object has certain properties.
* <p/>
* <h2>Example Usage</h2>
* Consider the situation where we have a class representing a person, which
* follows the basic JavaBean convention of having get() and possibly set()
* methods for it's properties: <code>
* public class Person {
* private String name;
* <p/>
* public Person(String person) {
* this.person = person;
* }
* <p/>
* public String getName() {
* return name;
* }
* }
* </code> And that these person
* objects are generated within a piece of code under test (a class named
* PersonGenerator). This object is sent to one of our mock objects which
* overrides the PersonGenerationListener interface: <code>
* public interface PersonGenerationListener {
* public void personGenerated(Person person);
* }
* </code>
* In order to check that the code under test generates a person with name
* "Iain" we would do the following:
* <p/>
* <code>
* Mock personGenListenerMock = mock(PersonGenerationListener.class);
* personGenListenerMock.expects(once()).method("personGenerated").with(and(isA(Person.class), hasProperty("Name", eq("Iain")));
* PersonGenerationListener listener = (PersonGenerationListener)personGenListenerMock.proxy();
* </code>
* <p/>
* If an exception is thrown by the getter method for a property, the property
* does not exist, is not readable, or a reflection related exception is thrown
* when trying to invoke it then this is treated as an evaluation failure and
* the matches method will return false.
* <p/>
* This matcher class will also work with JavaBean objects that have explicit
* bean descriptions via an associated BeanInfo description class. See the
* JavaBeans specification for more information:
* <p/>
* http://java.sun.com/products/javabeans/docs/index.html
*
* @author Iain McGinniss
* @author Nat Pryce
* @author Steve Freeman
*/
public class HasPropertyWithValue<T> extends TypeSafeDiagnosingMatcher<T> {
private final String propertyName;
private final Matcher<?> valueMatcher;
public HasPropertyWithValue(String propertyName, Matcher<?> valueMatcher) {
this.propertyName = propertyName;
this.valueMatcher = valueMatcher;
}
@Override
public boolean matchesSafely(T bean, Description mismatchDescription) {
try {
Method readMethod = findReadMethod(bean, mismatchDescription);
if (readMethod == null) {
return false;
}
Object propertyValue = readMethod.invoke(bean, NO_ARGUMENTS);
boolean valueMatches = valueMatcher.matches(propertyValue);
if (!valueMatches) {
mismatchDescription.appendText("property \"" + propertyName + "\" ");
valueMatcher.describeMismatch(propertyValue, mismatchDescription);
}
return valueMatches;
} catch (IllegalArgumentException e) {
return false;
} catch (IllegalAccessException e) {
return false;
} catch (InvocationTargetException e) {
return false;
}
}
private Method findReadMethod(Object argument, Description mismatchDescription) throws IllegalArgumentException {
PropertyDescriptor property = PropertyUtil.getPropertyDescriptor(propertyName, argument);
if (null == property) {
mismatchDescription.appendText("No property \"" + propertyName + "\"");
return null;
}
Method readMethod = property.getReadMethod();
if (null == readMethod) {
mismatchDescription.appendText("property \"" + propertyName + "\" is not readable");
}
return readMethod;
}
public void describeTo(Description description) {
description.appendText("hasProperty(");
description.appendValue(propertyName);
description.appendText(", ");
description.appendDescriptionOf(valueMatcher);
description.appendText(")");
}
@Factory
public static <T> Matcher<T> hasProperty(String propertyName, Matcher<?> value) {
return new HasPropertyWithValue<T>(propertyName, value);
}
}